/*
 * fail/0
 * !/0
 * var/1
 * compound/1
 * write_character/1
 * write_string/1
 * read_character/1
 * read_line/1
 * =../2
 * atom_codes/2
 * functor/3
 * findall/3
 *
 */

import java.io.*;
import java.util.IdentityHashMap;

public class KitchenSink extends Sink {
    public KitchenSink(Sink next) {
        this.next = next;
    }
    
    public Stack<Choicepoint> fishFor
        (String name, int arity, Compound term, int choice,
         Prolog prolog, Unifier unifier, int cutIndex,
         Stack<Conjunct> continuation, Stack<Choicepoint> choicepoints) {
        Choicepoint choicepoint;
        int height = choicepoints.height;
        
        switch(arity) {
        case 0:
            if(name.equals("fail")) {
                if(choice == 0) {
                    choicepoints.head.dead = true;
                    return choicepoints;
                }
            }
            else if(name.equals("!")) {
                if(choice == 0) {
                    for(;height != cutIndex; height--) {
                        if(choicepoints.tail != null)
                            choicepoints.tail.head.addBindings(choicepoints.head.bindings);
                        choicepoints = choicepoints.tail;
                    }
                    return new Stack<Choicepoint>(new Choicepoint(continuation,height),choicepoints);
                }
            }
            else {
                break;
            }
            choicepoints.head.dead = true;
            return choicepoints;
            
        case 1:
            if(name.equals("write")) { // TODO, replace this with an actual pretty printer
                if(choice == 0) {
                    System.out.print(term.args[0]);
                    return new Stack<Choicepoint>(new Choicepoint(continuation,height),choicepoints);
                }
            }
            else if(name.equals("var")) {
                if(choice == 0 &&
                   term.args[0].dereference().kind == Term.Kind.VARIABLE) {
                    return new Stack<Choicepoint>(new Choicepoint(continuation,height),choicepoints);
                }
            }
            else if(name.equals("compound")) {
                if(choice == 0 &&
                   term.args[0].dereference().kind == Term.Kind.COMPOUND) {
                    return new Stack<Choicepoint>(new Choicepoint(continuation,height),choicepoints);
                }
            }
            else if(name.equals("write_character")) {
                if(choice == 0) {
                    System.out.print((char)Integer.parseInt(((Compound)term.args[0].dereference()).name));
                    return new Stack<Choicepoint>(new Choicepoint(continuation,height),choicepoints);
                }
            }
            else if(name.equals("write_string")) {
                if(choice == 0) {
                    System.out.print(((Compound)term.args[0].dereference()).fromPrologString());
                    return new Stack<Choicepoint>(new Choicepoint(continuation,height),choicepoints);
                }
            }
            else if(name.equals("read_character")) {
                if(choice == 0) {
                    try {
                        String readCharacter = ""+(int)(new BufferedReader(new InputStreamReader(System.in)).readLine().charAt(0));
                        if(unifier.unify(term.args[0],new Compound(readCharacter))) {
                            choicepoint = new Choicepoint(continuation,height);
                            choicepoint.bindings = unifier.bound;
                            return new Stack<Choicepoint>(choicepoint,choicepoints);
                        }
                    } catch(Exception e) {}
                }
            }
            else if(name.equals("read_line")) {
                if(choice == 0) {
                    try {
                        String readString = new BufferedReader(new InputStreamReader(System.in)).readLine();
                        if(unifier.unify(term.args[0],Compound.toPrologString(readString))) {
                            choicepoint = new Choicepoint(continuation,height);
                            choicepoint.bindings = unifier.bound;
                            return new Stack<Choicepoint>(choicepoint,choicepoints);
                        }
                    } catch(Exception e) {}
                }
            }
            else {
                break;
            }
            choicepoints.head.dead = true;
            return choicepoints;
            
        case 2:
            if(name.equals("=..")) {
                if(choice == 0) {
                    Term q = term.args[0].dereference();
                    if(q.kind == Term.Kind.COMPOUND) {
                        Compound qc = (Compound)q;
                        
                        Compound flat = new Compound("[]");
                        for(int i = qc.arity-1; i >= 0; i--) {
                            flat = new Compound(".", qc.args[i], flat);
                        }
                        flat = new Compound(".", new Compound(qc.name), flat);
                        
                        if(unifier.unify(term.args[1],flat)) {
                            choicepoint = new Choicepoint(continuation,height);
                            choicepoint.bindings = unifier.bound;
                            return new Stack<Choicepoint>(choicepoint,choicepoints);
                        }
                    }
                    else {
                        if(unifier.unify(term.args[0],new Compound(term.args[1].fromPrologList()))) {
                            choicepoint = new Choicepoint(continuation,height);
                            choicepoint.bindings = unifier.bound;
                            return new Stack<Choicepoint>(choicepoint,choicepoints);
                        }
                    }
                }
            }
            else if(name.equals("atom_codes")) {
                if(choice == 0) {
                    Term q = term.args[0].dereference();
                    if(q.kind == Term.Kind.COMPOUND) {
                        Compound qc = (Compound)q;
                        
                        Compound codes = new Compound("[]");
                        for(int i = qc.name.length()-1; i >= 0; i--) {
                            codes = new Compound(".", new Compound(""+(int)qc.name.charAt(i)), codes);
                        }
                        
                        if(unifier.unify(term.args[1],codes)) {
                            choicepoint = new Choicepoint(continuation,height);
                            choicepoint.bindings = unifier.bound;
                            return new Stack<Choicepoint>(choicepoint,choicepoints);
                        }
                    }
                    else {
                        Stack<Term> codes = term.args[1].fromPrologList();
                        String str = "";
                        while(codes != null) {
                            str += (char)Integer.parseInt(((Compound)codes.head.dereference()).name);
                            codes = codes.tail;
                        }
                        if(unifier.unify(term.args[0],new Compound(str))) {
                            choicepoint = new Choicepoint(continuation,height);
                            choicepoint.bindings = unifier.bound;
                            return new Stack<Choicepoint>(choicepoint,choicepoints);
                        }
                    }
                }
            }
            else if(name.equals("get_file_contents")) {
                if(choice == 0) {
                    try {
                        String filename = ((Compound)term.args[0].dereference()).name;
                        BufferedReader reader = new BufferedReader(new FileReader(filename));
                        String line;
                        String fileContents = "";
                        while((line = reader.readLine()) != null) { fileContents += line+"\n"; }
                        if(unifier.unify(term.args[1],Compound.toPrologString(fileContents))) {
                            choicepoint = new Choicepoint(continuation,height);
                            choicepoint.bindings = unifier.bound;
                            return new Stack<Choicepoint>(choicepoint,choicepoints);
                        }
                    } catch(Exception e) {}
                }
            }
            else {
                break;
            }
            choicepoints.head.dead = true;
            return choicepoints;
            
        case 3:
            if(name.equals("functor")) {
                if(choice == 0) {
                    Term q = term.args[0].dereference();
                    if(q.kind == Term.Kind.COMPOUND) {
                        Compound qc = (Compound)q;
                        if(unifier.unify(term.args[1],new Compound(qc.name)) &&
                           unifier.unify(term.args[2],new Compound(""+qc.arity),unifier.bound)) {
                            choicepoint = new Choicepoint(continuation,height);
                            choicepoint.bindings = unifier.bound;
                            return new Stack<Choicepoint>(choicepoint,choicepoints);
                        }
                    }
                    else {
                        String qqname = ((Compound)term.args[1].dereference()).name;
                        int qarity = Integer.parseInt(((Compound)term.args[2].dereference()).name);
                        
                        System.out.println("%% TODO: NOT YET IMPLEMENTED FUNCTOR/3");
                    }
                }
            }
            else if(name.equals("findall")) {
                if(choice == 0) {
                    Prolog subengine = prolog.spawn(term.args[1].dereference());
                    Term result;
                    Compound left = null, right = null;
                    
                    while(subengine.run()) {
                        result = term.args[0].copy_term(new IdentityHashMap<Term,Term>());
                        if(right == null) {
                            left = new Compound(".",result,null);
                            right = left;
                        }
                        else {
                            right.args[1] = new Compound(".",result,null);
                            right = (Compound)right.args[1];
                        }
                    }
                    if(right == null) {
                        left = new Compound("[]");
                    }
                    else {
                        right.args[1] = new Compound("[]");
                    }
                    
                    if(unifier.unify(term.args[2],left)) {
                        choicepoint = new Choicepoint(continuation,height);
                        choicepoint.bindings = unifier.bound;
                        return new Stack<Choicepoint>(choicepoint,choicepoints);
                    }
                }
            }
            else {
                break;
            }
            choicepoints.head.dead = true;
            return choicepoints;
        }
        
        return next.fishFor
            (name,arity,term,choice,
             prolog,unifier,cutIndex,
             continuation,choicepoints);
    }
}

